<template>
    <el-dialog class="tnxel-dialog"
        :data-v-id="id"
        v-model="visible"
        destroy-on-close
        append-to-body
        :modal="options.modal"
        :close-on-click-modal="options['close-on-click-modal']"
        :close-on-press-escape="options['close-on-press-escape']"
        :show-close="options['show-close']"
        :center="options.center"
        :before-close="beforeClose"
        @closed="onClosed">
        <template #header>
            <div class="tnxel-dialog-title" :class="mergeClass({'border-bottom': title})" v-html="title"
                v-if="title || options['show-close']"></div>
        </template>
        <template v-if="$slots.default">
            <slot></slot>
        </template>
        <div :class="mergeClass()" v-html="contentValue" v-else-if="contentValue"></div>
        <tnxel-dialog-content :class="mergeClass()" ref="content" v-bind="contentProps" v-else></tnxel-dialog-content>
        <template #footer v-if="buttons && buttons.length">
            <div class="tnxel-dialog-footer" :class="mergeClass()">
                <el-button v-for="(button, index) in buttons" :type="button.type" :key="index"
                    :loading="buttonLoadings[index]" @click="btnClick(index)">{{ button.caption || button.text }}
                </el-button>
            </div>
        </template>
    </el-dialog>
</template>

<script>
import $ from 'cash-dom';
import DialogContent from './DialogContent.vue';

export default {
    name: 'TnxelDialog',
    components: {
        'tnxel-dialog-content': DialogContent,
    },
    props: {
        id: {
            type: String,
            default: () => window.tnx.util.string.uuid32(),
        },
        modelValue: Boolean,
        container: String,
        title: String,
        content: String,
        contentProps: Object,
        buttons: Array,
        theme: String,
        width: {
            type: [Number, String],
            default: 512,
        },
    },
    emits: ['update:modelValue', 'shown', 'closed'],
    data() {
        return {
            visible: this.modelValue,
            contentValue: this.content,
            options: {
                modal: true, // 是否需要遮罩层
                'close-on-click-modal': false, // 是否可以通过点击遮罩层关闭对话框
                'close-on-press-escape': true, // 是否可以通过按下 ESC 关闭对话框
                'show-close': true, // 是否显示关闭按钮
                center: false, // 是否对头部和底部采用居中布局
                width: this.width,
                // 以上均为element的Dialog组件配置项
                onShown: undefined, // 对话框展示后的事件回调
                onClosed: undefined, // 对话框关闭后的事件回调
            },
            middleTop: '40vh',
            buttonLoadings: [],
            heightChangeObserver: null,
        };
    },
    computed: {
        dialogTop() {
            if (typeof this.options.top === 'function') {
                return this.options.top();
            } else if (this.options.top) {
                return this.options.top;
            } else {
                return this.middleTop;
            }
        },
        dialogWidth() {
            if (typeof this.options.width === 'function') {
                return this.options.width();
            } else if (typeof this.options.width === 'number') {
                return this.options.width + 'px';
            } else {
                return this.options.width;
            }
        },
    },
    watch: {
        modelValue(newValue, oldValue) {
            this.visible = this.modelValue;
            if (newValue && !oldValue) { // 从隐藏到显示
                this.$nextTick(() => {
                    this.locate(true);
                });
            }
        },
        visible() {
            this.$emit('update:modelValue', this.visible);
        },
    },
    mounted() {
        window.tnx.app.eventBus.once('tnx.error', options => {
            this.buttonLoadings = [];
        });
        this.$nextTick(() => {
            if (this.visible) {
                this.locate(true);
            }

            if (this.$refs.content && !this.$refs.content.close) {
                this.$refs.content.close = () => {
                    this.close();
                }
            }

            if (typeof this.options.onShown === 'function') {
                this.options.onShown.call(this);
            } else {
                this.$emit('shown');
            }
        });
    },
    beforeUnmount() {
        window.tnx.app.eventBus.off('tnx.error');
    },
    methods: {
        mergeClass(classObject) {
            classObject = classObject || {};
            if (this.theme) {
                classObject['theme-' + this.theme] = true;
            }
            return classObject;
        },
        locate(observe) {
            let $dialog = undefined;
            if (this.container) {
                let $container = $(this.container);
                if ($container.length) {
                    $dialog = $container.next('.el-overlay').find('.el-dialog');
                }
            }
            if (!$dialog || !$dialog.length) {
                $dialog = $('.tnxel-dialog[data-v-id="' + this.id + '"]');
            }
            if ($dialog.length) {
                let top = window.tnx.util.dom.getTopVerticallyCenteredOnPage($dialog[0]);
                this.middleTop = top + 'px';
                $dialog.css({
                    'margin-top': this.dialogTop,
                    'width': this.dialogWidth,
                    'max-height': 'calc(100vh - 16px)', // 最大高度时上下各留8px空隙
                });

                if (observe) {
                    this.heightChangeObserver = window.tnx.util.dom.observeHeightChange($dialog[0], this.locate);
                }
            }
        },
        btnClick(index) {
            const button = this.buttons[index];
            if (button && typeof button.click === 'function') {
                let result = button.click.call(this.$refs.content, this.close);
                if (result === 'loading') {
                    this.buttonLoadings[index] = true;
                    return;
                }
                if (result === false) {
                    return;
                }
            }
            this.close();
        },
        close() {
            return new Promise((resolve) => {
                this.beforeClose(() => {
                    const originalOnClosed = this.options.onClosed;
                    this.options.onClosed = window.tnx.util.function.around(originalOnClosed, function (onClosed) {
                        if (onClosed) {
                            onClosed();
                        }
                        resolve();
                    });
                    this.visible = false;
                });
            });
        },
        beforeClose(done) {
            try {
                if (typeof this.options.beforeClose === 'function') {
                    if (this.options.beforeClose.call(this.$refs.content) === false) {
                        return;
                    }
                }
                done();
            } catch (e) { // 处理错误，以避免业务代码出错导致对话框无法关闭
                console.error(e);
            }
        },
        onClosed() {
            if (typeof this.options.onClosed === 'function') {
                this.options.onClosed.call(this.$refs.content);
            } else {
                this.$emit('closed');
            }
            if (this.heightChangeObserver) {
                this.heightChangeObserver.disconnect();
            }
        }
    }
}
</script>

<style>
.tnxel-dialog {
    display: none;
}

.tnxel-dialog.el-dialog {
    display: flex;
    flex-direction: column;
    margin-bottom: 0;
}

.tnxel-dialog .el-dialog__header {
    padding: 0;
}

.tnxel-dialog .el-dialog__header .el-dialog__headerbtn {
    width: 48px;
    height: 48px;
}

.tnxel-dialog .tnxel-dialog-title {
    padding: 0.75rem 1rem;
    font-size: 1rem;
}

.tnxel-dialog .tnxel-dialog-title > :last-child,
.tnxel-dialog .el-dialog__body > div > :last-child {
    margin-bottom: 0;
}

.tnxel-dialog .el-dialog__footer {
    padding: 0;
}

.tnxel-dialog .tnxel-dialog-footer {
    padding: 1rem;
    display: flex;
    justify-content: flex-end;
    border-top: 1px solid var(--el-border-color);
}

.tnxel-dialog .el-dialog__footer .el-button {
    margin-left: 10px;
}
</style>
